﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;

using ScreenMonkey.Net;
using System.ServiceModel;

namespace ScreenMonkey.Remote
{
    /// <summary>
    /// Code behind for SMClipSelect. The code is taken from the Screen Monkey
    /// remote sample and adjusted to the needs of the CeetGui. To mention is
    /// that naming conventions aren't applied as has been defined for the CeetGui
    /// development, thus many names here are the same as in the sample code.
    ///
    /// The connection with Screen Monkey can be provided through the Connection
    /// property by assignment or binding or by an event that corresponds to the
    /// OnConnectionChanged() handler of this class.
    /// 
    /// Note that it has been tried to provide the connection via the DataContext
    /// but things are getting too complicated due to the DataContext propagation,
    /// i.e. if the DataContext of the container of this control is changed the
    /// control gets that DataContext too which could overwrite a valid connection.
    /// 
    /// Another note. Since the connection is used over and over for data retrieval
    /// all code in this class that uses it must do appropriate error handling because
    /// the connection can get broken without this class being notified about it from
    /// the outside.
    /// </summary>
    public partial class SMClipSelect : UserControl
    {

        private SMConnection smNet = null;

        protected SMConnection SmNet
        {
            get { return smNet; }
            set 
            {
                if (smNet != value)
                {
                    if (smNet != null)
                    {
                        smNet.ConnectionChanged -= smNet_ConnectionChanged;
                    }

                    smNet = value;

                    if (smNet != null)
                    {
                        smNet.ConnectionChanged += smNet_ConnectionChanged;
                    }
                }
            }
        }

        private delegate void VoidHandler();

        void smNet_ConnectionChanged(object sender, EventArgs e)
        {
            Console.WriteLine("Connection Changed: {0} {1}", SmNet.IsConnected(), sender.ToString());

            if (SmNet.IsConnected())
            {
                SmNet.ClipLibrary.ClipAdded += ClipLibrary_ClipAdded;
                SmNet.ClipLibrary.ClipRemoved += ClipLibrary_ClipRemoved;
            }

            Dispatcher.BeginInvoke(new VoidHandler(InitializeClips));
        }


        /// <summary>
        /// 
        /// </summary>
        public SMClipSelect()
        {
            InitializeComponent();

            //Attach to the SM Net connection established event.
            SmNet = SMConnection.GetInstance();

            if (SmNet.IsConnected())
                InitializeClips();
        }

        void ClipLibrary_ClipRemoved(object sender, ClipInformation e)
        {
            Dispatcher.BeginInvoke(new VoidHandler(InitializeClips));
        }

        void ClipLibrary_ClipAdded(object sender, ClipInformation e)
        {
            Dispatcher.BeginInvoke(new VoidHandler(InitializeClips));
        }

        /// <summary>
        /// The connection to a Screen Monkey process which is of type SMConnection
        /// or null if no connection is established. If the Connection changes its
        /// state the user interface of the control is adjusted accordingly. Making
        /// this property easy bindable is the main reason why it is exposed as object
        /// and not as SMConnection. Another reason for the object type is encapsulation,
        /// allowing this class to hide any SM-specific data types from the outer world.
        /// Note that the connection can also be obtained by handling the ConnectionChanged
        /// event.
        /// </summary>
        public object Connection
        {
            get { return (object)GetValue(ConnectionProperty); }

            set
            {
                SetValue(ConnectionProperty, value);
                SmNet = value as SMConnection;         // Internal use
                InitializeClips();
            }
        }

        /// <summary>
        /// Using a DependencyProperty as the backing store for the Connection.
        /// This enables animation, styling, binding, etc.
        /// </summary>
        public static readonly DependencyProperty ConnectionProperty =
            DependencyProperty.Register("Connection",
                                        typeof(object),
                                        typeof(SMClipSelect),
                                        new FrameworkPropertyMetadata(new PropertyChangedCallback(ChangeConnection)));

        private static void ChangeConnection(DependencyObject source, 
                                             DependencyPropertyChangedEventArgs e)
        {
            (source as SMClipSelect).Connection = (object)e.NewValue;
        }

        /// <summary>
        /// 
        /// </summary>
        private void LoadViewModels()
        {
            if (smNet.IsConnected())
            {
                try
                {
                    PageStride = SmNet.Panel.PageStride;
                    if (PageStride <= 0)
                        PageStride = 100;

                    for (int n = 0; n < PageStride; n++)
                    {
                        Clips.Add(new SMClipViewModel());
                    }

                    foreach (PageInformation page in SmNet.ClipLibrary.Pages)
                    {
                        SMPageViewModel model = new SMPageViewModel(page);

                        model.LoadThumbnail(SmNet.ClipLibrary.GetPageThumbnail(page.Index - 1, PageStride, 100, 100));
                        Pages.Add(model);
                    }
                }
                catch (CommunicationException)
                {
                    Clear();
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        private void LoadClips()
        {
            if (smNet.IsConnected())
            {
                PageInformation page = SmNet.ClipLibrary.LookupPage(PageIndex + 1);

                if (page != null)
                {
                    PageName = page.Name;
                }
                else
                {
                    PageName = string.Format("Page {0}", PageIndex + 1);
                }

                foreach (SMClipViewModel model in Clips)
                {
                    model.Clip = null;
                }

                List<int> notifyClips = new List<int>();

                int handleOffset = PageStride * PageIndex;

                foreach (ClipInformation clip in SmNet.ClipLibrary.LookupClips(PageIndex, PageStride))
                {
                    int clipIndex = clip.Handle - handleOffset;

                    if (clipIndex >= 0 && clipIndex < Clips.Count)
                    {
                        SMClipViewModel model = Clips[clipIndex];

                        model.Clip = clip;
                        model.LoadThumbnail(SmNet.ClipLibrary.GetClipThumbnail(clip.Id, 100, 100));

                        notifyClips.Add(clip.Id);
                    }
                }

                SmNet.Clips.SubscribeToClip(notifyClips);
                SmNet.Clips.ClipChanged += new EventHandler<ClipChangedData>(OnClipChanged);
                SmNet.Clips.ThumbnailChanged += new EventHandler<ClipChangedData>(Clips_ThumbnailChanged);
            }
        }

        void Clips_ThumbnailChanged(object sender, ClipChangedData e)
        {
            Dispatcher.BeginInvoke(new LoadClipHandler(LoadClip), e.Clip);
        }

        private void LoadClip(ClipInformation clipInfo)
        {
            if(SmNet.IsConnected())
            {            
                SMClipViewModel model = Clips.FirstOrDefault(item => (item.Clip != null && item.Clip.Id == clipInfo.Id));

                if (model != null)
                {
                    model.Clip = clipInfo;
                }
            }
        }

        void OnClipChanged(object sender, ClipChangedData e)
        {
            Dispatcher.BeginInvoke(new LoadClipHandler(LoadClip), e.Clip);
        }

        private delegate void LoadClipHandler(ClipInformation clipInfo);

        #region View Model

        /// <summary>
        /// 
        /// </summary>
        public int PageIndex
        {
            get { return (int)GetValue(PageIndexProperty); }
            set { SetValue(PageIndexProperty, value); }
        }

        /// <summary>
        /// Using a DependencyProperty as the backing store for PageIndex.
        /// This enables animation, styling, binding, etc.
        /// </summary>
        public static readonly DependencyProperty PageIndexProperty =
            DependencyProperty.Register("PageIndex", 
                                        typeof(int), 
                                        typeof(SMClipSelect), 
                                        new UIPropertyMetadata(0));

        /// <summary>
        /// 
        /// </summary>
        public string PageName
        {
            get { return (string)GetValue(PageNameProperty); }
            set { SetValue(PageNameProperty, value); }
        }

        /// <summary>
        /// Using a DependencyProperty as the backing store for PageName.
        /// This enables animation, styling, binding, etc.
        /// </summary>
        public static readonly DependencyProperty PageNameProperty =
            DependencyProperty.Register("PageName",
                                        typeof(string), 
                                        typeof(SMClipSelect), 
                                        new UIPropertyMetadata(string.Empty));

        /// <summary>
        /// 
        /// </summary>
        public int PageStride
        {
            get { return (int)GetValue(PageStrideProperty); }
            set { SetValue(PageStrideProperty, value); }
        }

        /// <summary>
        /// Using a DependencyProperty as the backing store for PageStride.
        /// This enables animation, styling, binding, etc.
        /// </summary>
        public static readonly DependencyProperty PageStrideProperty =
            DependencyProperty.Register("PageStride",
                                        typeof(int), 
                                        typeof(SMClipSelect), 
                                        new UIPropertyMetadata(100));

        /// <summary>
        /// 
        /// </summary>
        public ObservableCollection<SMClipViewModel> Clips  
        {
            get { return (ObservableCollection<SMClipViewModel>)GetValue(ClipsProperty); }
            set { SetValue(ClipsProperty, value); }
        }

        /// <summary>
        /// Using a DependencyProperty as the backing store for Clips.
        /// This enables animation, styling, binding, etc.
        /// </summary>
        public static readonly DependencyProperty ClipsProperty =
            DependencyProperty.Register("Clips",
                                        typeof(ObservableCollection<SMClipViewModel>),
                                        typeof(SMClipSelect), 
                                        new UIPropertyMetadata(new ObservableCollection<SMClipViewModel>()));

        /// <summary>
        /// 
        /// </summary>
        public ObservableCollection<SMPageViewModel> Pages
        {
            get { return (ObservableCollection<SMPageViewModel>)GetValue(PagesProperty); }
            set { SetValue(PagesProperty, value); }
        }

        /// <summary>
        /// Using a DependencyProperty as the backing store for Pages.
        /// This enables animation, styling, binding, etc.
        /// </summary>
        public static readonly DependencyProperty PagesProperty =
            DependencyProperty.Register("Pages",
                                        typeof(ObservableCollection<SMPageViewModel>), 
                                        typeof(SMClipSelect), 
                                        new UIPropertyMetadata(new ObservableCollection<SMPageViewModel>()));

        #endregion

        private void clipList_MouseDown(object sender, MouseButtonEventArgs e)
        {
            // Placeholder
        }

        private void clipList_PreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
            SMClipViewModel viewModel = ((FrameworkElement)e.OriginalSource).DataContext as SMClipViewModel;

            if (viewModel != null && viewModel.Clip != null)
            {
                SmNet.Screen.PlayClip(SmLayers.Active, viewModel.Clip.Id);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        private void pageList_PreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
            SMPageViewModel viewModel = ((FrameworkElement)e.OriginalSource).DataContext as SMPageViewModel;

            if (viewModel != null && viewModel.Page != null)
            {
                PageIndex = viewModel.Page.Index - 1;
                LoadClips();

                clipList.Visibility = System.Windows.Visibility.Visible;
                pageList.Visibility = System.Windows.Visibility.Collapsed;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        private void clearLayer_PreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
            if(SmNet.IsConnected())
                SmNet.Screen.ClearScreen();
        }

        /// <summary>
        /// Toggle between page list and clip list  in the user interface.
        /// </summary>
        private void buttonPageSelect_Click(object sender, RoutedEventArgs e)
        {
            if (clipList.Visibility == Visibility.Visible)
            {
                ShowPageList();
            }
            else
            {
                ShowClipList();
            }
        }

        /// <summary>
        /// Load Screen Monkey data if available and update the
        /// user interface.
        /// </summary>
        private void InitializeClips()
        {
            Clear();

            LoadViewModels();
            LoadClips();
        }

        /// <summary>
        /// Remove all data and update the user interface.
        /// </summary>
        private void Clear()
        {
            Clips.Clear();
            Pages.Clear();

            ShowClipList();

            PageStride = 0;
            PageIndex = 0;
            PageName = "";
        }

        /// <summary>
        /// Helper method to show the page for clip selection.
        /// </summary>
        private void ShowClipList()
        {
            clipList.Visibility = Visibility.Visible;
            pageList.Visibility = Visibility.Collapsed;
        }

        /// <summary>
        /// Helper method to show the page for page selection.
        /// </summary>
        private void ShowPageList()
        {
            clipList.Visibility = Visibility.Collapsed;
            pageList.Visibility = Visibility.Visible;
        }

        private void sliderVolume_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            SmNet.Layers.SetVolume(SmLayers.Audio, (float) (e.NewValue / 100.0));
        }
    }
}
